package br.biofoco.p2p.scheduling;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Map.Entry;

import br.biofoco.p2p.peer.ID;
import br.biofoco.p2p.peer.PeerNode;
import br.biofoco.p2p.utils.Pair;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Multimap;
import com.jcraft.jsch.Logger;

public class WQRScheduler implements TaskScheduler {

	private static final int REPLICATION_FACTOR = 3;

	private static final Random random = new Random(System.nanoTime());

	private final Multimap<PeerNode, Task> schedulingMap = HashMultimap
			.create();

	public synchronized void clear() {
		schedulingMap.clear();
	}
	
	public void printMap() {
		Map<PeerNode, Collection<Task>> printMap = schedulingMap.asMap();
	    
	    for (PeerNode id : printMap.keySet()){
	    	System.out.println("PEER: " + id);
	    	for (Task task : printMap.get(id)){
	    		System.out.println("|--->Task: " + task);
	    	}
	    }
	}	

	public synchronized Multimap<PeerNode, Task> schedule(Collection<PeerNode> peerView, Collection<? extends Task> bagOfTasks) {

		Iterator<PeerNode> machines = Iterators.cycle(peerView);

		for (Task task : bagOfTasks) {
			if ((task.getStatus() != TaskStatus.COMPLETED) && (task.getStatus() != TaskStatus.ABORTED))
				schedulingMap.put(machines.next(), task);
		}

//		Iterator<PeerNode> machines = peerView.iterator();
//
//		for (Task task : bagOfTasks) {
//			if ((task.getStatus() != TaskStatus.COMPLETED) && (task.getStatus() != TaskStatus.ABORTED)){
//				if (!machines.hasNext())
//					machines = peerView.iterator();
//				schedulingMap.put(machines.next(), task);
//			}			
//		}
//		
//		if (machines.hasNext()) {
//			Iterator<? extends Task> taskIt = bagOfTasks.iterator();
//			while (machines.hasNext()) {
//				schedulingMap.put(machines.next(), taskIt.next());
//			}
//		}

		return schedulingMap;
	}

	public synchronized void schedule(Collection<? extends Task> tasks) {
		Iterator<PeerNode> machines = Iterators.cycle(schedulingMap.keySet());
		
		for (Task task : tasks) {
			if (task.getStatus() != TaskStatus.COMPLETED && task.getStatus() != TaskStatus.ABORTED)
				schedulingMap.put(machines.next(), task);
		}
	}

	public void removePeer(ID peer) {
		Collection<Task> tasks = schedulingMap.removeAll(peer);
		schedule(tasks);
	}

	// TODO: CREATE FORWARDING MULTIMAP
	public Multimap<PeerNode, Task> getSchedulingMap() {
		return schedulingMap;
	}

	// TODO: E se a tarefa estiver executando na maq "A" quando terminar na maq
	// "B"?
	public synchronized void removeFinishedTasks() {

		for (Iterator<Entry<PeerNode, Task>> entries = schedulingMap.entries()
				.iterator(); entries.hasNext();) {
			Entry<PeerNode, Task> entry = entries.next();
			TaskStatus currStatus = entry.getValue().getStatus();
			if (currStatus == TaskStatus.COMPLETED) {
				entries.remove();
			}
		}

	}

	public synchronized void relocateAbortedTasks() {

		for (Iterator<Entry<PeerNode, Task>> entries = schedulingMap.entries()
				.iterator(); entries.hasNext();) {
			Entry<PeerNode, Task> entry = entries.next();
			TaskStatus currStatus = entry.getValue().getStatus();

			if (currStatus == TaskStatus.ABORTED) {

				PeerNode id = null;

				do {

					List<PeerNode> tentatives = new ArrayList<PeerNode>(
							schedulingMap.keySet());

					id = tentatives.get(random.nextInt(tentatives.size()));

				} while (entry.getKey().equals(id));

				schedulingMap.put(id, entry.getValue());
				entry.getValue().setStatus(TaskStatus.PENDING);
				entries.remove();
			}
		}

	}

	public Pair<PeerNode, Integer> getMinQueueSize() {

		int minLength = Integer.MAX_VALUE;
		PeerNode minPeer = null;

		for (PeerNode peer : schedulingMap.keySet()) {
			int queueSize = schedulingMap.get(peer).size();
			if (queueSize < minLength) {
				minPeer = peer;
				minLength = queueSize;
			}
		}

		return Pair.of(minPeer, minLength);
	}

	public Pair<PeerNode, Integer> getMaxQueueSize() {

		int maxLength = Integer.MIN_VALUE;
		PeerNode maxPeer = null;

		for (PeerNode peer : schedulingMap.keySet()) {
			int queueSize = schedulingMap.get(peer).size();
			if (queueSize > maxLength) {
				maxPeer = peer;
				maxLength = queueSize;
			}
		}

		return Pair.of(maxPeer, maxLength);
	}

	public void replicateTask() {
		Pair<PeerNode, Integer> maxPair = getMaxQueueSize();

		if (maxPair.second > 0) {
			Pair<PeerNode, Integer> minPair = getMinQueueSize();

			if (minPair.second != maxPair.second) {
				Task task = getTaskForReplication(maxPair.first);
				if (task != null)
					schedulingMap.put(minPair.first, task);
			}
		}
	}

	private Task getTaskForReplication(PeerNode peer) {

		ArrayList<Task> list = new ArrayList<Task>(schedulingMap.get(peer));
		Collections.reverse(list);

		for (Task task : list) {
			if (task.getReplicaCount() < REPLICATION_FACTOR) {
				task.increaseReplicaCount();
				return task;
			}
		}
		return null;
	}

	public synchronized void updateStatus(long taskID, TaskStatus status) {

		Collection<Task> tasks = schedulingMap.values();

		for (Task task : tasks) {
			if (task.getID() == taskID) {
				task.setStatus(status);
			}
		}
	}

	public synchronized void updateScheduling() {
		System.out.println("********************** atualizando escalonador *********************************");
		removeFinishedTasks();
		relocateAbortedTasks();
		replicateTask();
	}

	public void balanceTasks() {

		Pair<PeerNode, Integer> pair = getMinQueueSize();

		while (pair.second == 0) {
			replicateTask();
			pair = getMinQueueSize();
		}
	}
	
	public Task removeHeadFrom(PeerNode node) {
		
		Iterator<Task> it = schedulingMap.get(node).iterator();
		
		Task t = null;
		if (it.hasNext()) {
			t = it.next();
			it.remove();
		}
		
		return t;
		
	}

	// private static class UnmodifiableMultimap extends ForwardingMultimap<ID,
	// Task> {
	//		
	// private Multimap<ID, Task> delegate;
	//
	// public UnmodifiableMultimap(Multimap<ID,Task> delegate) {
	// this.delegate = delegate;
	// }
	//
	// @Override
	// protected Multimap<ID, Task> delegate() {
	// return delegate;
	// }
	//		
	//		
	// }
}
